import java.util.Random;


public class MapView {
	
	private Field[][] map;
	protected String[] direction = {"left", "up", "right", "down"};
	private int GAMESIZE;
	
	public MapView(Field[][] map) {
		this.map = map;
		GAMESIZE = map.length;
	}
	
	public Field[][] getMap() {
		return map;
	}
	
	/**
	 * Determines whether the fields at position from and position to is next to each other
	 * @param from
	 * @param to
	 * @return true if the fields are next to each other and false, else.
	 */
	public boolean isNextTo(Position from, Position to) {
		int dx = Math.abs(from.getRow() - to.getRow());
		int dy = Math.abs(from.getColumn() - to.getColumn());
		if((dx == 1 || dy == 1) && dy + dx == 1) return true;
		else return false;
	}
	
	/**
	 * Sets up the map. E.g if GAMESIZE = 3, the map will look like:
	 * 1 2 3
	 * 4 5 6
	 * 7 8 e
	 * Where e is the empty field
	 */
	public void setupMap() {
		for(int x = 1; x <= GAMESIZE; x++) {
			for(int y = 1; y <= GAMESIZE; y++) {
				int number = (y-1)*GAMESIZE + x;
				map[x-1][y-1] = new Field(new Integer(number));
			}
		}
		Field f = map[GAMESIZE-1][GAMESIZE-1];
		f.setNumber(new Integer(0));
	}
	
	/**
	 * Scrambles the map by moving the empty field the value of difficulty times.
	 * Note that this is by no means the most correct algorithm for scrambling the map.
	 * E.g. by calling scramble(16) we could wind up in the situation where the map hasn't
	 * been changed at all. The probability of this, however is 4^-16. It is, however
	 * probably the easiest algorithm. I believe the problem is solved (not completely, and 
	 * I am not 100% sure about this) by, from user-input, calculating some sort of internal-
	 * difficulty value with the use of prime numbers, powers, etc.
	 * @param difficulty the allowed moves to scramble the map.
	 */
	public void scramble(int difficulty) {
		setupMap();
		int d = difficulty;
		Random r = new Random();
		Position nullPos = new Position(GAMESIZE, GAMESIZE);
		while(canMove(nullPos, direction[r.nextInt(4)]) && d>0) {
			d--;
			nullPos = getNullPos();
		}
		
	}
	
	/**
	 * @return the position of the empty field
	 */
	public Position getNullPos() {
		for(int x = 1; x <= GAMESIZE; x++) 
			for(int y = 1; y <= GAMESIZE; y++) 
				if(getFieldAt(new Position(x,y)).getNumber() == 0)
					return new Position(x,y);
		return null;
	}
	
	/**
	 * This probably should catch an IndexOutOfBoundsException,
	 * but since the method is private, I am sure it will only
	 * be called internally, where I myself will take care that
	 * it only will be called with the correct parameters.
	 * @param position the position to return the field at
	 * @return the field at the Position position
	 */
	public Field getFieldAt(Position pos) {
		return map[pos.getColumn()-1][pos.getRow()-1];
	}
	
	public Field getFieldAt(int x, int y) {
		return getFieldAt(getPosition(x, y));
	}
	/**
	 * @param nullPos the position of the empty field
	 * @param direction the direction the empty field should be moved
	 * @return true if the empty field was moved and false, else.
	 */
	public boolean canMove(Position nullPos, String direction) {
		if(direction == "left") {
			if(nullPos.getColumn()-1 > 0) {
				move(nullPos, direction);
				return true;
			}
		} else if(direction == "up") {
			if(nullPos.getRow()-1 > 0) {
				move(nullPos, direction);
				return true;
			}
		} else if(direction == "right") {
			if(nullPos.getColumn()+1 <= GAMESIZE) {
				move(nullPos, direction);
				return true;
			}
		} else if(direction == "down") {
			if(nullPos.getRow()+1 <= GAMESIZE) {
				move(nullPos, direction);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Moves the empty field
	 * @param nullPos the position of the (empty) field
	 * @param direction the direction to move the (empty) field at position nullPos
	 */
	public void move(Position nullPos, String direction) {
		if(direction == "left") {
			Position to = new Position(nullPos.getColumn()-1,nullPos.getRow());
			Field tmp = getFieldAt(to);
			Field from = getFieldAt(nullPos);
			from.setNumber(tmp.getNumber());
			tmp.setNumber(new Integer(0));
		} else if(direction == "up") {
			Position to = new Position(nullPos.getColumn(),nullPos.getRow()-1);
			Field tmp = getFieldAt(to);
			Field from = getFieldAt(nullPos);
			from.setNumber(tmp.getNumber());
			tmp.setNumber(new Integer(0));
		} else if(direction == "right") {
			Position to = new Position(nullPos.getColumn()+1,nullPos.getRow());
			Field tmp = getFieldAt(to);
			Field from = getFieldAt(nullPos);
			from.setNumber(tmp.getNumber());
			tmp.setNumber(new Integer(0));
		} else if(direction == "down") {
			Position to = new Position(nullPos.getColumn(),nullPos.getRow()+1);
			Field tmp = getFieldAt(to);
			Field from = getFieldAt(nullPos);
			from.setNumber(tmp.getNumber());
			tmp.setNumber(new Integer(0));
		}
	}
	
	public Object clone() {
		Field[][] clone = new Field[GAMESIZE][GAMESIZE];
		for(int i = 0; i < GAMESIZE; i++) {
			for(int j = 0; j < GAMESIZE; j++) {
				clone[i][j] = (Field) map[i][j].clone();
			}
		}
		return new MapView(clone);
	}
	
	public boolean equals(Object o) {
		if(o.getClass() != this.getClass()) return false;
		else {
			MapView other = (MapView) o;
			return other.getMap().equals(map);
		}
	}

	public Position getPosition(int x, int y) {
		int diffX = x%100;
		int diffY = y%100;
		x -= diffX;
		y -= diffY;
		return new Position(x/100+1, y/100+1);
	}

	public void move(Position nullPos, Position to) {
		String dir;
		int dc = to.getColumn() - nullPos.getColumn();
		int dr = to.getRow() - nullPos.getRow();
		if(dc > 0) dir = "right";
		else if(dc < 0) dir = "left";
		else if(dr > 0) dir = "down";
		else dir = "up";
		move(nullPos, dir);
	}

	public boolean isSolved() {
		for(int i = 1; i <= GAMESIZE; i++) {
			for(int j = 1; j <= GAMESIZE; j++) {
				int number = (j-1)*GAMESIZE + i;
				if(map[i-1][j-1].getNumber().intValue() == number
				|| (number == GAMESIZE*GAMESIZE
						&& map[i-1][j-1].getNumber().intValue() == 0)) {
					return false;
				}
			}
		}
		return true;
	}
	
}
